上一篇我們拆解了NT Headers的架構
了解他分成三個部分(Signature / File Header / Optional Header)
還有分別在硬碟和記憶體放置資料時的Alignment
在這篇文章中,會先提到Optional Header的最後一個成員Data Directories
再來會講Data Directories和Section Headers還有Section之間的關係
最後偷偷講Export Directory
來當例子
Data Directories是Optional Header的最後一個成員
因為他所提供的資訊跟PE檔的中後段比較有關,所以沒有在頭部的時候講
主要儲存的是各Data Directory的位置(entry)跟他的大小
底下是他的structure (defined in winnt.h)
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
整個Data Directories總共有16個entries
從PE Bear可以看到全部的Directories
而順著VirtualAddress找到的各個Directory的Structure不會一樣
舉Debug Directory跟Export Directory為例
每個Directory也會根據其性質被放在對應的Section
檔案中的資料(除了Header)都會放在Section裡面
以下是Section的列表
有些Section會被併入其他的Section裡面,下面kernel32.dll就是一個例子
在Section Headers找不到.edata區段
且Export Directory(0x97710 ~ 0xA5554)被包在.rdata區段(0x7D800 ~ 0xB0600)裡面
可以知道放引出函式的資料(.edata)被併入.rdata區段了
事實上,這些Section的名稱並不重要
Section的類別主要是被區段內存放的資料所定義
放置各個Section的Raw Address、Virtual Address、權限資訊(Characteristics)
還有Relocation和Debug相關(LineNumber)的資料
下面是Section Headers的structure
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
如果Section內所存的值是固定的,則SizeOfRawData >= VirtualSize
因為SizeOfRawData必須要是FileAlignment的倍數,而VirtualSize則否
如果Section內所存的值是會變動的(.bss),則VirtualSize >= SizeOfRawData
在程式執行時,這些數值會被assign
因此在記憶體上的大小(VirtualSize)就會大於在磁碟上的大小(SizeOfRawData)
PointerToLinenumbers和NumberOfLinenumbers儲存的都是跟Debug相關的資訊
如果PE檔的Debug資訊有被strip掉,則這兩個欄位的值為 0
舉Export Directory來當例子看看
這個Directory存放的是在動態連結時引出函式的相關資訊
底下是他的structure
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions;
DWORD AddressOfNames;
DWORD AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;
這邊簡單講一下比較重要的member
補充資訊:
在這個部分,我們會嘗試實作出PE Bear在Export Directory底下的資訊欄
附上layout當作參考
詳細的步驟:
// 找到Optional Header (from NT Headers)
IMAGE_DOS_HEADER *dosHeader = (IMAGE_DOS_HEADER *)fbuf;
IMAGE_NT_HEADERS *ntHeaders = (IMAGE_NT_HEADERS *)((size_t)dosHeader + dosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER optHeader = &ntHeaders->OptionalHeader;
// 找到Export Descriptor (from DataDiretory in Optional Header)
IMAGE_DATA_DIRECTORY exportDescriptor = optHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
// 得出Section總數,並找出Export Descriptor是在哪個Section裡面
PIMAGE_SECTION_HEADER sectionHeader = (PIMAGE_SECTION_HEADER)(((PBYTE)optHeader) + ntHeaders->FileHeader.SizeOfOptionalHeader);
WORD numberOfSections = ntHeaders->FileHeader.NumberOfSections;
int i;
DWORD VirtualAddr, VirtualSize;
for (i = 0; i < numberOfSections; i++) {
VirtualAddr = sectionHeader->VirtualAddress;
VirtualSize = sectionHeader->Misc.VirtualSize;
if (VirtualAddr <= exportDescriptor.VirtualAddress &&
exportDescriptor.VirtualAddress < VirtualAddr + VirtualSize) {
break;
}
sectionHeader = (PIMAGE_SECTION_HEADER)(((PBYTE)sectionHeader) + sizeof(IMAGE_SECTION_HEADER));
}
RVA轉File Offset
轉換步驟:
總而言之就是
File Offset = RVA - section在記憶體上的開始位置 + section在硬碟上的開始位置
// RVA轉成File Offset
DWORD RVA2FileOffset(DWORD RVA, PIMAGE_SECTION_HEADER sectionHeader)
{
return RVA - sectionHeader->VirtualAddress + sectionHeader->PointerToRawData;
}
算出各個數值
找forwarder的方式
forwarder會緊接在function name後面
如果發現下一個兩個function name之間的大小 != function name長度 + 1
就可以知道中間有插一個forwarder
以上面這個例子:
// 列出全部的引出函式
for (i = 0; i < exportDirectory->NumberOfFunctions; i++) {
int addrDiff;
char *functionName, *forwarderName;
functionName = (char *)((size_t)dosHeader + RVA2FileOffset(*(AddressOfNames + i), sectionHeader));
if (i < exportDirectory->NumberOfFunctions - 1) {
addrDiff = *(AddressOfNames + i + 1) - *(AddressOfNames + i);
if (addrDiff != strlen((char *)functionName) + 1) {
forwarderName = functionName + strlen(functionName) + 1;
} else {
forwarderName = NULL;
}
} else {
addrDiff = strlen((char *)functionName);
}
printf("%08x%8s%8x%8s%012x%8s%08x%8s%-50s%-50s\n",
RVA2FileOffset(exportDirectory->AddressOfFunctions + i * 4, sectionHeader), " ",
*(AddressOfOrdinals + i) + exportDirectory->Base, " ",
*(AddressOfFunctions + i), " ",
*(AddressOfNames + i), " ",
functionName,
forwarderName == NULL ? " " : (char *)forwarderName);
}
完成後dump出kernel32.dll的export directory,結果如下
原本以為可以收工,結果試另外一個Imagehlp.dll就烙賽了 = =
檢查了一下發現他的Ordinal跟Name RVA不是照順序排下來的,哭了
所以修正過後的步驟是:
// 用來存數值的struct
// 其實functionNameAddr不用assign成指標
struct _exportTable {
DWORD offset;
WORD ordinal;
DWORD *functionPtr;
DWORD *functionNameAddr;
char *functionName;
char *forwarderName;
};
// 挖空間給struct
struct _exportTable **exportTable =
malloc(sizeof(struct _exportTable *) * exportDirectory->NumberOfFunctions);
for (i = 0; i < exportDirectory->NumberOfFunctions; i++)
exportTable[i] = malloc(sizeof(struct _exportTable));
// print出來的function
void printExportTable(struct _exportTable **exportTable, size_t len)
{
printf("Offset%10sOrdinals%8sFunction RVA%8sName RVA%8s%-50s%-50s\n",
" ", " ", " ", " ", "Name", "Forwarder");
for (int i = 0; i < len; i++) {
printf("%08x%8s%8x%8s%012x%8s%08x%8s%-50s%-50s\n",
exportTable[i]->offset, " ",
exportTable[i]->ordinal, " ",
*(exportTable[i]->functionPtr), " ",
*(exportTable[i]->functionNameAddr), " ",
exportTable[i]->functionName,
exportTable[i]->forwarderName);
}
return;
}
改完之後就照順序排下來了
source code (successfully built on Windows11 w/ gcc 11.2.0)
這篇我們介紹了Data Directories (AKA 各個Directory的Entry跟Size的Container)
還提到各種Section和用來存Section開始位置的Section Headers
最後寫了一個小程式來重現PE Bear的export functions欄位
下一篇我們會講到Import Dir / Resource Dir / Base Relocation Table
0xrick's PE file format
https://0xrick.github.io/win-internals/pe5/
https://0xrick.github.io/win-internals/pe6/
Official Documentation
https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header
Sections
https://stackoverflow.com/questions/19012300/whats-the-difference-between-rdata-and-idata-segments
RVA to File Offset
https://stackoverflow.com/questions/9955744/getting-offset-in-file-from-rva
Stuxnet
https://css.csail.mit.edu/6.858/2014/readings/stuxnet.pdf
https://nixhacker.com/fixing-dll-exports-for-dll-hijacking/